use std::str::FromStr;

use actix_web::{web, HttpResponse};
use wither::bson::oid::ObjectId;

use crate::{
    errors::cons_error,
    models::{
        profile::ProfileModelDTO,
        user::{NormalUserModel, UserRole},
    },
    services::profile_service::{CreateProfileSvrReq, ProfileService, UpdateProfileSvrReq},
    utils::authorize::check_user_has_permission_with_object_id,
    AppError, AppResult, PaginationQuery,
};

mod ctrl_struct {
    use std::str::FromStr;

    use wither::bson::oid::ObjectId;

    use crate::{
        services::profile_service::{CreateProfileSvrReq, UpdateProfileSvrReq},
        utils::db_utils::{json_to_document, UpdateField},
        AppError,
    };

    define_filter_request_struct!(SearchProfileReq {});
    define_request_struct!(ProfileDetailReq { profile_id: String });
    define_request_struct!(UpdateProfile {
        profile_id:String,
        namespace:Option<String>,
        #[serde(default="Default::default")]
        data:UpdateField<serde_json::Value>,
        max_history:Option<u8>
    });
    define_request_struct!(CreateProfile {
        namespace: String,
        data: Option<serde_json::Value>,
        max_history: u8
    });
    impl TryFrom<UpdateProfile> for UpdateProfileSvrReq {
        type Error = AppError;
        fn try_from(value: UpdateProfile) -> Result<Self, Self::Error> {
            Ok(Self {
                profile_id: ObjectId::from_str(&value.profile_id)?,
                namespace: value.namespace,
                data: value.data.map(|f| json_to_document(&f).unwrap_or_default()),
                max_history: value.max_history,
            })
        }
    }
    impl TryFrom<CreateProfile> for CreateProfileSvrReq {
        type Error = AppError;
        fn try_from(value: CreateProfile) -> Result<Self, Self::Error> {
            Ok(Self {
                namespace: value.namespace,
                data: value.data.as_ref().and_then(json_to_document),
                max_history: value.max_history,
            })
        }
    }
}

async fn search_profile(
    svr: web::Data<ProfileService>,
    req: web::Json<ctrl_struct::SearchProfileReq>,
    query: web::Query<PaginationQuery>,
) -> AppResult<HttpResponse> {
    let filter = req.get_filter_as_document_use_default();

    let res = svr.search(filter, &query.0).await?;

    Ok(HttpResponse::Ok().json(res.map_into::<ProfileModelDTO>()))
}

async fn update_profile(
    svr: web::Data<ProfileService>,
    user: NormalUserModel,
    req: web::Json<ctrl_struct::UpdateProfile>,
) -> AppResult<HttpResponse> {
    if !check_user_has_permission_with_object_id(&user, None, UserRole::Admin) {
        return Err(AppError::ConsError(cons_error::MSG_REQUIRE_AUTHORIZE));
    }
    let req: UpdateProfileSvrReq = req.into_inner().try_into()?;
    let res = svr.update(user, req).await?;

    Ok(HttpResponse::Ok().json(ProfileModelDTO::from(res)))
}
async fn profile_detail(
    svr: web::Data<ProfileService>,
    req: web::Json<ctrl_struct::ProfileDetailReq>,
) -> AppResult<HttpResponse> {
    let id = ObjectId::from_str(&req.profile_id)?;
    let res = svr.detail(id).await?;

    Ok(HttpResponse::Ok().json(ProfileModelDTO::from(res)))
}
async fn create_profile(
    svr: web::Data<ProfileService>,
    user: NormalUserModel,
    req: web::Json<ctrl_struct::CreateProfile>,
) -> AppResult<HttpResponse> {
    if !check_user_has_permission_with_object_id(&user, None, UserRole::Admin as u8) {
        return Err(AppError::ConsError(cons_error::MSG_REQUIRE_AUTHORIZE));
    }
    let req: CreateProfileSvrReq = req.into_inner().try_into()?;
    let res = svr.create(req).await?;

    Ok(HttpResponse::Ok().json(ProfileModelDTO::from(res)))
}

pub fn configure_profile_controller(config: &mut web::ServiceConfig) {
    config
        .route("/search", web::post().to(search_profile))
        .route("/update", web::post().to(update_profile))
        .route("/detail", web::post().to(profile_detail))
        .route("/create", web::post().to(create_profile));
}
